梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
回溯算法的本质:它是一种深度优先搜索(DFS)的暴力枚举策略,核心是“尝试 - 回退”—— 在解决问题的过程中,一步步尝试所有可能的选择,当发现当前选择无法得到合法解时,就回退到上一步,撤销当前选择,继续尝试其他可能,直到找到所有解 / 一个合法解。
问题描述:
算法解析:
N 皇后的核心是逐行放置皇后(天然避免同行冲突),再通过回溯法尝试每一行的每一列,同时提前校验列、正对角线、反对角线是否已有皇后(剪枝,避免无效递归),具体思路:
关键技巧:冲突快速校验
直接遍历棋盘校验冲突的时间复杂度较高,用三个布尔数组记录占用状态,能实现 O(1) 时间校验:
#include <iostream>
#include <vector>
using namespace std;
vector<vector<string>> res_nQueens; // 存储所有合法方案
vector<string> board; // 模拟棋盘,每一行是一个字符串
vector<bool> col; // 标记列是否被占用
vector<bool> diag1; // 标记正对角线(i-j)是否被占用
vector<bool> diag2; // 标记反对角线(i+j)是否被占用
int n; // 棋盘大小
// 回溯函数:处理第row行的皇后放置
void nQueens(int row) {
// 终止条件:所有行都放置了皇后,找到合法方案
if (row == n) {
res_nQueens.push_back(board);
return;
}
// 遍历当前行的所有列,尝试放置皇后
for (int j = 0; j < n; ++j) {
// 计算对角线索引,避免diag1下标为负
int d1 = row - j + n - 1;
int d2 = row + j;
// 剪枝:列/正对角线/反对角线有皇后,跳过该列
if (col[j] || diag1[d1] || diag2[d2]) {
continue;
}
// 选择:在(row, j)放置皇后
board[row][j] = 'Q';
col[j] = true;
diag1[d1] = true;
diag2[d2] = true;
// 递归:处理下一行
nQueens(row + 1);
// 回溯:撤销选择,还原状态
board[row][j] = '*';
col[j] = false;
diag1[d1] = false;
diag2[d2] = false;
}
}
int main() {
n = 4;
// 初始化棋盘:n行,每行n个'*'
board.assign(n, string(n, '*'));
// 初始化标记数组,大小按需分配
col.assign(n, false);
diag1.assign(2 * n - 1, false);
diag2.assign(2 * n - 1, false);
// 从第0行开始回溯
nQueens(0);
for(vector<string> item : res_nQueens)
{
cout << "[";
for(string node : item )
{
cout << " " << node << " ";
}
cout << "]" << endl;
}
return 0;
}
[ *Q** ***Q Q*** **Q* ]
[ **Q* Q*** ***Q *Q** ]
问题描述:
算法解析:
解数独和 N 皇后的回溯逻辑一脉相承,但更复杂 —— 需要遍历棋盘所有空位置,为每个空位置尝试合法数字(1-9),同时通过快速校验剪枝,具体核心思路:
关键技巧:
#include <iostream>
#include <vector>
using namespace std;
int matrix = 9; // 数独固定为9×9
// 校验函数:判断在(i,j)位置填充数字c是否合法
bool isValid(vector<vector<char>>& board, int i, int j, char c) {
for (int k = 0; k < matrix; ++k) {
// 检查第i行是否有c(列遍历)
if (board[i][k] == c) return false;
// 检查第j列是否有c(行遍历)
if (board[k][j] == c) return false;
// 检查所属3×3子数独是否有c
// 核心:计算3×3子数独内的格子坐标 (i/3*3 + k/3, j/3*3 + k%3)
int x = i / 3 * 3 + k / 3;
int y = j / 3 * 3 + k % 3;
if (board[x][y] == c) return false;
}
return true;
}
// 回溯函数:填充数独,找到解返回true,否则返回false
bool sudoku(vector<vector<char>>& board) {
// 行优先遍历整个9×9棋盘
for (int i = 0; i < matrix; ++i) {
for (int j = 0; j < matrix; ++j) {
if (board[i][j] != '.') continue; // 跳过已填充的格子
// 尝试为当前空位置填充1-9的合法数字
for (char c = '1'; c <= '9'; ++c) {
if (isValid(board, i, j, c)) { // 校验数字c是否合法
// 选择:填充合法数字
board[i][j] = c;
// 递归:处理后续空位置,找到解则直接返回true(提前终止)
if (sudoku(board)) return true;
// 回溯:撤销选择,还原为空位置
board[i][j] = '.';
}
}
// 1-9都尝试过,无合法数字,返回false表示当前路径无解
return false;
}
}
// 遍历完所有格子,无空位置,说明数独已解,返回true
return true;
}
int main() {
vector<vector<char>> board = {
{'5','3','.','.','7','.','.','.','.'},
{'6','.','.','1','9','5','.','.','.'},
{'.','9','8','.','.','.','.','6','.'},
{'8','.','.','.','6','.','.','.','3'},
{'4','.','.','8','.','3','.','.','1'},
{'7','.','.','.','2','.','.','.','6'},
{'.','6','.','.','.','.','2','8','.'},
{'.','.','.','4','1','9','.','.','5'},
{'.','.','.','.','8','.','.','7','9'}
};
sudoku(board);
for(vector<char> item : board)
{
cout << "[";
for(char node : item )
{
cout << " " << node << " ";
}
cout << "]" << endl;
}
return 0;
}
[ 5 3 4 6 7 8 9 1 2 ]
[ 6 7 2 1 9 5 3 4 8 ]
[ 1 9 8 3 4 2 5 6 7 ]
[ 8 5 9 7 6 1 4 2 3 ]
[ 4 2 6 8 5 3 7 9 1 ]
[ 7 1 3 9 2 4 8 5 6 ]
[ 9 6 1 5 3 7 2 8 4 ]
[ 2 8 7 4 1 9 6 3 5 ]
[ 3 4 5 2 8 6 1 7 9 ]